/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
class CryptoMd5 {
    public static final int TIME_MULTIPLIER = 1000;
    public static final int MD5_NUM_2 = 2;
    public static final int MD5_NUM_3 = 3;
    public static final int MD5_NUM_4 = 4;
    public static final int MD5_NUM_5 = 5;
    public static final int MD5_NUM_6 = 6;
    public static final int MD5_NUM_7 = 7;
    public static final int MD5_NUM_8 = 8;
    public static final int MD5_NUM_9 = 9;
    public static final int MD5_NUM_10 = 10;
    public static final int MD5_NUM_11 = 11;
    public static final int MD5_NUM_12 = 12;
    public static final int MD5_NUM_13 = 13;
    public static final int MD5_NUM_14 = 14;
    public static final int MD5_NUM_15 = 15;
    public static final int MD5_NUM_16 = 16;
    public static final int MD5_NUM_17 = 17;
    public static final int MD5_NUM_20 = 20;
    public static final int MD5_NUM_21 = 21;
    public static final int MD5_NUM_22 = 22;
    public static final int MD5_NUM_23 = 23;
    public static final int MD5_NUM_32 = 32;
    public static final int MD5_NUM_64 = 64;
    public static final int MD5_NUM_120 = 20;
    public static final int MD5_NUM_128 = 128;
    public static final int MD5_NUM_512 = 512;

    public static final int MD5_NUM_38016083 = 38016083;
    public static final int MD5_NUM_568446438 = 568446438;
    public static final int MD5_NUM_606105819 = 606105819;
    public static final int MD5_NUM_643717713 = 643717713;
    public static final int MD5_NUM_681279174 = 681279174;
    public static final int MD5_NUM_1200080426 = 1200080426;
    public static final int MD5_NUM_1770035416 = 1770035416;
    public static final int MD5_NUM_1804603682 = 1804603682;
    public static final int MD5_NUM_1163531501 = 1163531501;
    public static final int MD5_NUM_1735328473 = 1735328473;
    public static final int MD5_NUM_1839030562 = 1839030562;
    public static final int MD5_NUM_1272893353 = 1272893353;

    public static final int MD5_NUM_76029189 = 76029189;
    public static final int MD5_NUM_1126891415 = 1126891415;
    public static final int MD5_NUM_530742520 = 530742520;
    public static final int MD5_NUM_1700485571 = 1700485571;

    public static final int MD5_NUM_1873313359 = 1873313359;
    public static final int MD5_NUM_1309151649 = 1309151649;
    public static final int MD5_NUM_718787259 = 718787259;
    public static final int MD5_NUM_1236535329 = 1236535329;

    public static final int MD5_NUM_NEGATIVE_343485551 = -343485551;
    public static final int MD5_NUM_NEGATIVE_1120210379 = -1120210379;
    public static final int MD5_NUM_NEGATIVE_145523070 = -145523070;
    public static final int MD5_NUM_NEGATIVE_1560198380 = -1560198380;

    public static final int MD5_NUM_NEGATIVE_30611744 = -30611744;
    public static final int MD5_NUM_NEGATIVE_2054922799 = -2054922799;
    public static final int MD5_NUM_NEGATIVE_1051523 = -1051523;
    public static final int MD5_NUM_NEGATIVE_1894986606 = -1894986606;

    public static final int MD5_NUM_NEGATIVE_57434055 = -57434055;
    public static final int MD5_NUM_NEGATIVE_1416354905 = -1416354905;
    public static final int MD5_NUM_NEGATIVE_198630844 = -198630844;
    public static final int MD5_NUM_NEGATIVE_995338651 = -995338651;

    public static final int MD5_NUM_NEGATIVE_421815835 = -421815835;
    public static final int MD5_NUM_NEGATIVE_640364487 = -640364487;
    public static final int MD5_NUM_NEGATIVE_722521979 = -722521979;
    public static final int MD5_NUM_NEGATIVE_358537222 = -358537222;

    public static final int MD5_NUM_NEGATIVE_1094730640 = -1094730640;
    public static final int MD5_NUM_NEGATIVE_155497632 = -155497632;
    public static final int MD5_NUM_NEGATIVE_1530992060 = -1530992060;
    public static final int MD5_NUM_NEGATIVE_35309556 = -35309556;

    public static final int MD5_NUM_NEGATIVE_2022574463 = -2022574463;
    public static final int MD5_NUM_NEGATIVE_378558 = -378558;
    public static final int MD5_NUM_NEGATIVE_1926607734 = -1926607734;
    public static final int MD5_NUM_NEGATIVE_51403784 = -51403784;

    public static final int MD5_NUM_NEGATIVE_1444681467 = -1444681467;
    public static final int MD5_NUM_NEGATIVE_187363961 = -187363961;
    public static final int MD5_NUM_NEGATIVE_1019803690 = -1019803690;
    public static final int MD5_NUM_NEGATIVE_405537848 = -405537848;
    public static final int MD5_NUM_NEGATIVE_660478335 = -660478335;
    public static final int MD5_NUM_NEGATIVE_701558691 = -701558691;
    public static final int MD5_NUM_NEGATIVE_373897302 = -373897302;
    public static final int MD5_NUM_NEGATIVE_1069501632 = -1069501632;

    public static final int MD5_NUM_NEGATIVE_165796510 = -165796510;
    public static final int MD5_NUM_NEGATIVE_1502002290 = -1502002290;
    public static final int MD5_NUM_NEGATIVE_40341101 = -40341101;
    public static final int MD5_NUM_NEGATIVE_1990404162 = -1990404162;
    public static final int MD5_NUM_NEGATIVE_42063 = -42063;
    public static final int MD5_NUM_NEGATIVE_1958414417 = -1958414417;
    public static final int MD5_NUM_NEGATIVE_45705983 = -45705983;
    public static final int MD5_NUM_NEGATIVE_1473231341 = -1473231341;
    public static final int MD5_NUM_NEGATIVE_176418897 = -176418897;
    public static final int MD5_NUM_NEGATIVE_1044525330 = -1044525330;
    public static final int MD5_NUM_NEGATIVE_389564586 = -389564586;
    public static final int MD5_NUM_NEGATIVE_680876936 = -680876936;

    public static final int MD5_NUM_0X80 = 0x80;
    public static final int MD5_NUM_0X3F = 0x3f;
    public static final int MD5_NUM_0XF = 0xf;
    public static final int MD5_NUM_0XFF = 0xff;
    public static final int MD5_NUM_0XFFFF = 0xffff;
    public static final int MD5_NUM_0X36363636 = 0x36363636;
    public static final int MD5_NUM_0X5C5C5C5C = 0x5c5c5c5c;

    public static boolean inDebug = false;
    public static void log(String str) {
        if (inDebug) {
            System.out.println(str);
        }
    }
    public static double currentTimestamp13() {
        return System.nanoTime() / 1000_000.0;
    }


    /*
     * Configurable variables. You may need to tweak these to be compatible with
     * the server-side, but the defaults work in most cases.
     */
    public static final int hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase        */
    public static final String b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance   */
    public static final int chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode      */

    /*
     * These are the funcs you'll usually want to call
     * They take string arguments and return either hex or base-64 encoded strings
     */
    public static String hexMd5(String s) {
        return binl2hex(coreMd5(str2binl(s), s.length() * chrsz));
    }
    public String b64Md5(String s) {
        return binl2b64(coreMd5(str2binl(s), s.length() * chrsz));
    }
    public String strMd5(String s) {
        return binl2str(coreMd5(str2binl(s), s.length() * chrsz));
    }
    public String hexHmacMd5(String key, String data) {
        return binl2hex(coreHmacMd5(key, data));
    }
    public String b64HmacMd5(String key, String data) {
        return binl2b64(coreHmacMd5(key, data));
    }
    public String strHmacMd5(String key, String data) {
        return binl2str(coreHmacMd5(key, data));
    }

    /*
     * Perform a simple self-test to see if the VM is working
     */
    public boolean md5VmTest() {
        return hexMd5("abc").equals("900150983cd24fb0d6963f7d28e17f72");
    }

    /*
     * Calculate the MD5 of an array of little-endian words, and a bit length
     */
    public static ArrayList<Integer> coreMd5(ArrayList<Integer> x1, int len) {
        ArrayList<Integer> x = x1;
        int toMax = (len >> MD5_NUM_5) - x.size();
        if (toMax >= 0) {
            x.addAll(new ArrayList<Integer>(Collections.nCopies(toMax + 1, 0)));
        }
        /* append padding */
        x.set(len >> MD5_NUM_5, x.get(len >> MD5_NUM_5) | (MD5_NUM_0X80 << len % MD5_NUM_32));

        int toMax1 = (((len + MD5_NUM_64) >>> MD5_NUM_9) << MD5_NUM_4) + MD5_NUM_14 - x.size();
        if (toMax1 >= 0) {
            x.addAll(new ArrayList<>(Collections.nCopies(toMax1 + 1, 0)));
        }
        x.set((((len + MD5_NUM_64) >>> MD5_NUM_9) << MD5_NUM_4) + MD5_NUM_14, len);

        int a = 1732584193;
        int b = -271733879;
        int c = -1732584194;
        int d = 271733878;
        int j = 0;
        while (j < x.size()) {
            int toMax2 = j + MD5_NUM_15 - x.size();
            if (toMax2 >= 0) {
                x.addAll(new ArrayList<>(Collections.nCopies(toMax2 + 1, 0)));
            }
            int olda = a;
            int oldb = b;
            int oldc = c;
            int oldd = d;

            a = md5Ff(a, b, c, d, x.get(j+0), MD5_NUM_7, MD5_NUM_NEGATIVE_680876936);
            d = md5Ff(d, a, b, c, x.get(j+1), MD5_NUM_12, MD5_NUM_NEGATIVE_389564586);
            c = md5Ff(c, d, a, b, x.get(j + MD5_NUM_2), MD5_NUM_17, MD5_NUM_606105819);
            b = md5Ff(b, c, d, a, x.get(j + MD5_NUM_3), MD5_NUM_22, MD5_NUM_NEGATIVE_1044525330);

            a = md5Ff(a, b, c, d, x.get(j + MD5_NUM_4), MD5_NUM_7, MD5_NUM_NEGATIVE_176418897);
            d = md5Ff(d, a, b, c, x.get(j + MD5_NUM_5), MD5_NUM_12, MD5_NUM_1200080426);
            c = md5Ff(c, d, a, b, x.get(j + MD5_NUM_6), MD5_NUM_17, MD5_NUM_NEGATIVE_1473231341);
            b = md5Ff(b, c, d, a, x.get(j + MD5_NUM_7), MD5_NUM_22, MD5_NUM_NEGATIVE_45705983);

            a = md5Ff(a, b, c, d, x.get(j + MD5_NUM_8), MD5_NUM_7, MD5_NUM_1770035416);
            d = md5Ff(d, a, b, c, x.get(j + MD5_NUM_9), MD5_NUM_12, MD5_NUM_NEGATIVE_1958414417);
            c = md5Ff(c, d, a, b, x.get(j + MD5_NUM_10), MD5_NUM_17, MD5_NUM_NEGATIVE_42063);
            b = md5Ff(b, c, d, a, x.get(j + MD5_NUM_11), MD5_NUM_22, MD5_NUM_NEGATIVE_1990404162);

            a = md5Ff(a, b, c, d, x.get(j + MD5_NUM_12), MD5_NUM_7, MD5_NUM_1804603682);
            d = md5Ff(d, a, b, c, x.get(j + MD5_NUM_13), MD5_NUM_12, MD5_NUM_NEGATIVE_40341101);
            c = md5Ff(c, d, a, b, x.get(j + MD5_NUM_14), MD5_NUM_17, MD5_NUM_NEGATIVE_1502002290);
            b = md5Ff(b, c, d, a, x.get(j + MD5_NUM_15), MD5_NUM_22, MD5_NUM_1236535329);

            a = md5Gg(a, b, c, d, x.get(j + 1), MD5_NUM_5, MD5_NUM_NEGATIVE_165796510);
            d = md5Gg(d, a, b, c, x.get(j + MD5_NUM_6), MD5_NUM_9, MD5_NUM_NEGATIVE_1069501632);
            c = md5Gg(c, d, a, b, x.get(j + MD5_NUM_11), MD5_NUM_14, MD5_NUM_643717713);
            b = md5Gg(b, c, d, a, x.get(j + 0), MD5_NUM_20, MD5_NUM_NEGATIVE_373897302);

            a = md5Gg(a, b, c, d, x.get(j + MD5_NUM_5), MD5_NUM_5, MD5_NUM_NEGATIVE_701558691);
            d = md5Gg(d, a, b, c, x.get(j + MD5_NUM_10), MD5_NUM_9, MD5_NUM_38016083);
            c = md5Gg(c, d, a, b, x.get(j + MD5_NUM_15), MD5_NUM_14, MD5_NUM_NEGATIVE_660478335);
            b = md5Gg(b, c, d, a, x.get(j + MD5_NUM_4), MD5_NUM_20, MD5_NUM_NEGATIVE_405537848);

            a = md5Gg(a, b, c, d, x.get(j + MD5_NUM_9), MD5_NUM_5, MD5_NUM_568446438);
            d = md5Gg(d, a, b, c, x.get(j + MD5_NUM_14), MD5_NUM_9, MD5_NUM_NEGATIVE_1019803690);
            c = md5Gg(c, d, a, b, x.get(j + MD5_NUM_3), MD5_NUM_14, MD5_NUM_NEGATIVE_187363961);
            b = md5Gg(b, c, d, a, x.get(j + MD5_NUM_8), MD5_NUM_20, MD5_NUM_1163531501);

            a = md5Gg(a, b, c, d, x.get(j + MD5_NUM_13), MD5_NUM_5, MD5_NUM_NEGATIVE_1444681467);
            d = md5Gg(d, a, b, c, x.get(j + MD5_NUM_2), MD5_NUM_9, MD5_NUM_NEGATIVE_51403784);
            c = md5Gg(c, d, a, b, x.get(j + MD5_NUM_7), MD5_NUM_14, MD5_NUM_1735328473);
            b = md5Gg(b, c, d, a, x.get(j + MD5_NUM_12), MD5_NUM_20, MD5_NUM_NEGATIVE_1926607734);

            a = md5Hh(a, b, c, d, x.get(j + MD5_NUM_5), MD5_NUM_4, MD5_NUM_NEGATIVE_378558);
            d = md5Hh(d, a, b, c, x.get(j + MD5_NUM_8), MD5_NUM_11, MD5_NUM_NEGATIVE_2022574463);
            c = md5Hh(c, d, a, b, x.get(j + MD5_NUM_11), MD5_NUM_16, MD5_NUM_1839030562);
            b = md5Hh(b, c, d, a, x.get(j + MD5_NUM_14), MD5_NUM_23, MD5_NUM_NEGATIVE_35309556);

            a = md5Hh(a, b, c, d, x.get(j + 1), MD5_NUM_4, MD5_NUM_NEGATIVE_1530992060);
            d = md5Hh(d, a, b, c, x.get(j + MD5_NUM_4), MD5_NUM_11, MD5_NUM_1272893353);
            c = md5Hh(c, d, a, b, x.get(j + MD5_NUM_7), MD5_NUM_16, MD5_NUM_NEGATIVE_155497632);
            b = md5Hh(b, c, d, a, x.get(j + MD5_NUM_10), MD5_NUM_23, MD5_NUM_NEGATIVE_1094730640);

            a = md5Hh(a, b, c, d, x.get(j + MD5_NUM_13), MD5_NUM_4, MD5_NUM_681279174);
            d = md5Hh(d, a, b, c, x.get(j + 0), MD5_NUM_11, MD5_NUM_NEGATIVE_358537222);
            c = md5Hh(c, d, a, b, x.get(j + MD5_NUM_3), MD5_NUM_16, MD5_NUM_NEGATIVE_722521979);
            b = md5Hh(b, c, d, a, x.get(j + MD5_NUM_6), MD5_NUM_23, MD5_NUM_76029189);

            a = md5Hh(a, b, c, d, x.get(j + MD5_NUM_9), MD5_NUM_4, MD5_NUM_NEGATIVE_640364487);
            d = md5Hh(d, a, b, c, x.get(j + MD5_NUM_12), MD5_NUM_11, MD5_NUM_NEGATIVE_421815835);
            c = md5Hh(c, d, a, b, x.get(j + MD5_NUM_15), MD5_NUM_16, MD5_NUM_530742520);
            b = md5Hh(b, c, d, a, x.get(j + MD5_NUM_2), MD5_NUM_23, MD5_NUM_NEGATIVE_995338651);

            a = md5Ii(a, b, c, d, x.get(j + 0), MD5_NUM_6, MD5_NUM_NEGATIVE_198630844);
            d = md5Ii(d, a, b, c, x.get(j + MD5_NUM_7), MD5_NUM_10, MD5_NUM_1126891415);
            c = md5Ii(c, d, a, b, x.get(j + MD5_NUM_14), MD5_NUM_15, MD5_NUM_NEGATIVE_1416354905);
            b = md5Ii(b, c, d, a, x.get(j + MD5_NUM_5), MD5_NUM_21, MD5_NUM_NEGATIVE_57434055);

            a = md5Ii(a, b, c, d, x.get(j + MD5_NUM_12), MD5_NUM_6, MD5_NUM_1700485571);
            d = md5Ii(d, a, b, c, x.get(j + MD5_NUM_3), MD5_NUM_10, MD5_NUM_NEGATIVE_1894986606);
            c = md5Ii(c, d, a, b, x.get(j + MD5_NUM_10), MD5_NUM_15, MD5_NUM_NEGATIVE_1051523);
            b = md5Ii(b, c, d, a, x.get(j + 1), MD5_NUM_21, MD5_NUM_NEGATIVE_2054922799);

            a = md5Ii(a, b, c, d, x.get(j + MD5_NUM_8), MD5_NUM_6, MD5_NUM_1873313359);
            d = md5Ii(d, a, b, c, x.get(j + MD5_NUM_15), MD5_NUM_10, MD5_NUM_NEGATIVE_30611744);
            c = md5Ii(c, d, a, b, x.get(j + MD5_NUM_6), MD5_NUM_15, MD5_NUM_NEGATIVE_1560198380);
            b = md5Ii(b, c, d, a, x.get(j + MD5_NUM_13), MD5_NUM_21, MD5_NUM_1309151649);

            a = md5Ii(a, b, c, d, x.get(j + MD5_NUM_4), MD5_NUM_6, MD5_NUM_NEGATIVE_145523070);
            d = md5Ii(d, a, b, c, x.get(j + MD5_NUM_11), MD5_NUM_10, MD5_NUM_NEGATIVE_1120210379);
            c = md5Ii(c, d, a, b, x.get(j + MD5_NUM_2), MD5_NUM_15, MD5_NUM_718787259);
            b = md5Ii(b, c, d, a, x.get(j + MD5_NUM_9), MD5_NUM_21, MD5_NUM_NEGATIVE_343485551);

            a = safeAdd(a, olda);
            b = safeAdd(b, oldb);
            c = safeAdd(c, oldc);
            d = safeAdd(d, oldd);

            j += MD5_NUM_16;
        }
        return new ArrayList<>(Arrays.asList(a, b, c, d));
    }

    /*
     * These funcs implement the four basic operations the algorithm uses.
     */
    public static int md5Cmn(int q, int a, int b, int x, int s, int t) {
        return safeAdd(bitRol(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
    }
    public static int  md5Ff(int a, int b, int c, int d, int x, int s, int t) {
        return md5Cmn((b & c) | (~b & d), a, b, x, s, t);
    }
    public static int  md5Gg(int a, int b, int c, int d, int x, int s, int t) {
        return md5Cmn((b & d) | (c & ~d), a, b, x, s, t);
    }
    public static int  md5Hh(int a, int b, int c, int d, int x, int s, int t) {
        return md5Cmn(b ^ c ^ d, a, b, x, s, t);
    }
    public static int md5Ii(int a, int b, int c, int d, int x, int s, int t) {
        return md5Cmn(c ^ (b | ~d), a, b, x, s, t);
    }

    /*
     * Calculate the HMAC-MD5, of a key and some data
     */
    public ArrayList<Integer> coreHmacMd5(String key, String data) {
        ArrayList<Integer> bkey = str2binl(key);
        if (bkey.size() > MD5_NUM_16) {
            bkey = coreMd5(bkey, key.length() * chrsz);
        }
        int toMax = MD5_NUM_15 - bkey.size();
        if (toMax >= 0) {
            bkey.addAll(new ArrayList<>(Collections.nCopies(toMax + 1, 0)));
        }
        ArrayList<Integer> ipad = new ArrayList<>(MD5_NUM_16);
        ArrayList<Integer> opad = new ArrayList<>(MD5_NUM_16);
        for (int i = 0; i < MD5_NUM_16; i++) {
            ipad.set(i, bkey.get(i) ^ MD5_NUM_0X36363636);
            opad.set(i, bkey.get(i) ^ MD5_NUM_0X5C5C5C5C);
        }
        ipad.addAll(str2binl(data));
        ArrayList<Integer> hash = coreMd5(ipad, MD5_NUM_512 + data.length() * chrsz);
        opad.addAll(hash);
        return coreMd5(opad, MD5_NUM_512 + MD5_NUM_128);
    }

    /*
     * Add integers, wrapping at 2^32. This uses 16-bit operations internally
     * to work around bugs in some JS interpreters.
     */
    public static int safeAdd(int x, int y) {
        int lsw = (x & MD5_NUM_0XFFFF) + (y & MD5_NUM_0XFFFF);
        int msw = (x >> MD5_NUM_16) + (y >> MD5_NUM_16) + (lsw >> MD5_NUM_16);
        return (msw << MD5_NUM_16) | (lsw & MD5_NUM_0XFFFF);
    }

    /*
     * Bitwise rotate a 32-bit number to the left.
     */
    public static int bitRol(int num, int cnt) {
        return (num << cnt) | (num >>> (MD5_NUM_32 - cnt));
    }

    /*
     * Convert a string to an array of little-endian words
     * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
     */
    public static ArrayList<Integer> str2binl(String str) {
        ArrayList<Integer> bin = new ArrayList<>();
        int mask = (1 << chrsz) - 1;
        int toMax = ((str.length() * chrsz - 1) >> MD5_NUM_5) - bin.size();
        if (toMax >= 0) {
            bin.addAll(new ArrayList<>(Collections.nCopies(toMax + 1, 0)));
        }

        int i = 0;
        while (i < str.length() * chrsz) {
            bin.set(i >> MD5_NUM_5, bin.get(i >> MD5_NUM_5) | ((str.codePointAt(i / chrsz) & mask) << i % MD5_NUM_32));
            i += chrsz;
        }
        log("crypto-md5: str2binl = " + bin);
        return bin;
    }

    /*
     * Convert an array of little-endian words to a string
     */
    public String binl2str(ArrayList<Integer> bin) {
        String str = "";
        int mask = (1 << chrsz) - 1;
        for (int i = 0; i < bin.size() * MD5_NUM_32; i += chrsz) {
            str += String.valueOf((char) ((bin.get(i >> MD5_NUM_5) >>> (i % MD5_NUM_32)) & mask));;
        }
        return str;
    }

    /*
     * Convert an array of little-endian words to a hex string.
     */
    public static String binl2hex(ArrayList<Integer> binarray) {
        String hexTab = hexcase != 0 ? "0123456789ABCDEF" : "0123456789abcdef";
        String str = "";

        for (int i = 0; i < binarray.size() * MD5_NUM_4; i++) {
            str = str +
                    hexTab.charAt((binarray.get(i >> MD5_NUM_2) >> ((i % MD5_NUM_4) * MD5_NUM_8 + MD5_NUM_4)) & MD5_NUM_0XF) +
                    hexTab.charAt((binarray.get(i >> MD5_NUM_2) >> ((i % MD5_NUM_4) * MD5_NUM_8)) & MD5_NUM_0XF);
        }
        return str;
    }

    /*
     * Convert an array of little-endian words to a base-64 string
     */
    public String binl2b64(ArrayList<Integer> binarray)  {
        String tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        String str = "";
        int i = 0;

        while (i < binarray.size() * MD5_NUM_4) {
            int triplet =
                    (((getData(binarray, i >> MD5_NUM_2) >> (MD5_NUM_8 * (i % MD5_NUM_4))) & MD5_NUM_0XFF) << MD5_NUM_16) |
                            (((getData(binarray, (i + 1) >> MD5_NUM_2) >> (MD5_NUM_8 * ((i + 1) % MD5_NUM_4))) & MD5_NUM_0XFF) << MD5_NUM_8) |
                            ((getData(binarray, (i + MD5_NUM_2) >> MD5_NUM_2) >> (MD5_NUM_8 * ((i + MD5_NUM_2) % MD5_NUM_4))) & MD5_NUM_0XFF);
            for (int j = 0; j < MD5_NUM_4; j++) {
                if (i * MD5_NUM_8 + j * MD5_NUM_6 > binarray.size() * MD5_NUM_32) {
                    str += b64pad;
                } else {
                    str += tab.charAt((triplet >> (MD5_NUM_6 * (MD5_NUM_3 - j))) & MD5_NUM_0X3F);
                }
            }
            i += MD5_NUM_3;
        }

        return str;
    }
    public int getData(ArrayList<Integer> array, int index) {
        if (index >= array.size()) {
            return 0;
        }
        return array.get(index);
    }

    public static void run() {
        String plainText = """
Rebellious subjects, enemies to peace,\nProfaners \
of this neighbour-stained steel,--\n\
Will they not hear? What, ho! you men, you beasts,\n\
That quench the fire of your pernicious rage\n\
With purple fountains issuing from your veins,\nOn \
pain of torture, from those bloody hands\n\
Throw your mistemper'd weapons to the ground,\n\
And hear the sentence of your moved prince.\n\
Three civil brawls, bred of an airy word,\nBy thee, \
old Capulet, and Montague,\n\
Have thrice disturb'd the quiet of our streets,\n\
And made Verona's ancient citizens\n\
Cast by their grave beseeming ornaments,\n\
To wield old partisans, in hands as old,\nCanker'd \
with peace, to part your canker'd hate:\n\
If ever you disturb our streets again,\n\
Your lives shall pay the forfeit of the peace.\n\
For this time, all the rest depart away:\nYou Capulet; \
shall go along with me:\n\
And, Montague, come you this afternoon,\nTo \
know our further pleasure in this case,\n\
To old Free-town, our common judgment-place.\nOnce more, \
on pain of death, all men depart.""";

        for (int i = 0; i < MD5_NUM_4; i++) {
            plainText += plainText;
        }

        String md5Output = hexMd5(plainText);
        log("crypto-md5: md5Output = " + md5Output);
        String expected = "a831e91e0f70eddcb70dc61c6f82f6cd";
        if (!md5Output.equals(expected)) {
            throw new Error("ERROR: bad result: expected " + expected + " but got " + md5Output);
        }
    }
}

/*
 * @State
 * @Tags Jetstream2
 */
class Benchmark {
  /*
   * @Benchmark
   */
    public static void runIteration() {
        for (int i = 0; i < CryptoMd5.MD5_NUM_22; ++i) {
            CryptoMd5.run();
        }
    }

    public static void runCryptoMd5() {
        double startTime = CryptoMd5.currentTimestamp13();
        Benchmark benchmark = new Benchmark();
        for (int i = 0; i < CryptoMd5.MD5_NUM_120; i++) {
            double startTimeInLoop = CryptoMd5.currentTimestamp13();
            benchmark.runIteration();
            double endTimeInLoop = CryptoMd5.currentTimestamp13();
            // log('CryptoMd5: ms = ' +  (endTimeInLoop - startTimeInLoop) + ' i = ' + i)
        }
        double endTime = CryptoMd5.currentTimestamp13();
        System.out.println("crypto-md5: ms = " + (endTime - startTime));

    }

    public static void main(String[] args) {
        runCryptoMd5();
    }
}

